package hlcache

import (
	"context"
	"errors"
	"sync"
	"sync/atomic"
	"time"
)

var localCacheRunning atomic.Bool
var sitkCache CacheObj

// EnableLocalCache 启用本地缓存
func EnableLocalCache(ctx context.Context, maxItemNum int) (error, *CacheObj) {
	if localCacheRunning.Load() {
		return nil, &sitkCache
	}
	sitkCache = CacheObj{
		dataMap:     &sync.Map{},
		maxItemNum:  int64(maxItemNum),
		currItemNum: atomic.Int64{},
	}
	sitkCache.currItemNum.Store(0)
	localCacheRunning.Store(true)
	go clearExpiredCache(ctx, &sitkCache)
	return nil, &sitkCache
}

// GetLocalCacheObj 获取Cache对象
func GetLocalCacheObj() (error, *CacheObj) {
	if !localCacheRunning.Load() {
		return errors.New("local cache was not running! pls enable first! "), nil
	}
	return nil, &sitkCache
}

// Set 添加缓存，expiredTime为相对于当前时间多少秒之后
func (scObj *CacheObj) Set(key, value string, expiredTime uint) error {
	var ok bool
	var tmpCacheItem cacheItem
	if len(key) == 0 || len(value) == 0 {
		return ErrorZeroLength
	}
	if len(key) > MaxKeyLength {
		return ExceededMaxKeyLength
	}
	if len(value) > MaxValueLength {
		return ExceededMaxValueLength
	}
	if expiredTime > MaxExpiredTime {
		expiredTime = MaxExpiredTime
	}
	tmpCacheItem = cacheItem{
		expireTime: time.Now().UnixNano() + int64(expiredTime)*OneSecondNS,
		cacheValue: value,
	}
	if scObj.currItemNum.Load()+1 > scObj.maxItemNum {
		return ExceededMaxItem
	}
	if _, ok = scObj.dataMap.Load(key); !ok {
		scObj.currItemNum.Add(1)
	}
	scObj.dataMap.Store(key, tmpCacheItem)
	return nil
}

// SetWithSize 添加缓存并且指定value大小长度，expiredTime为相对于当前时间多少秒之后
func (scObj *CacheObj) SetWithSize(key, value string, valueSize int, expiredTime uint) error {
	var ok bool
	var tmpCacheItem cacheItem
	if len(key) == 0 || len(value) == 0 {
		return ErrorZeroLength
	}
	if len(key) > MaxKeyLength {
		return ExceededMaxKeyLength
	}
	if len(value) > valueSize {
		return ExceededMaxValueLength
	}
	if expiredTime > MaxExpiredTime {
		expiredTime = MaxExpiredTime
	}
	tmpCacheItem = cacheItem{
		expireTime: time.Now().UnixNano() + int64(expiredTime)*OneSecondNS,
		cacheValue: value,
	}
	if scObj.currItemNum.Load()+1 > scObj.maxItemNum {
		return ExceededMaxItem
	}
	if _, ok = scObj.dataMap.Load(key); !ok {
		scObj.currItemNum.Add(1)
	}
	scObj.dataMap.Store(key, tmpCacheItem)
	return nil
}

// Get 获取缓存
func (scObj *CacheObj) Get(key string) (error, string) {
	var ok bool
	var value any
	var tmpCacheItem cacheItem
	if len(key) > MaxKeyLength {
		return ExceededMaxKeyLength, ""
	}
	if value, ok = scObj.dataMap.Load(key); ok {
		if tmpCacheItem, ok = value.(cacheItem); ok {
			return nil, tmpCacheItem.cacheValue.(string)
		}
	}
	return KeyNotFound, ""
}

// GetWithDel 获取缓存并删除
func (scObj *CacheObj) GetWithDel(key string) (error, string) {
	var ok bool
	var value any
	var tmpCacheItem cacheItem
	if len(key) > MaxKeyLength {
		return ExceededMaxKeyLength, ""
	}
	if value, ok = scObj.dataMap.LoadAndDelete(key); ok {
		if tmpCacheItem, ok = value.(cacheItem); ok {
			scObj.currItemNum.Add(-1)
			return nil, tmpCacheItem.cacheValue.(string)
		}
	}
	return KeyNotFound, ""
}

// Del 删除缓存
func (scObj *CacheObj) Del(key string) error {
	var ok bool
	if len(key) > MaxKeyLength {
		return ExceededMaxKeyLength
	}
	if _, ok = scObj.dataMap.Load(key); ok {
		scObj.dataMap.Delete(key)
		scObj.currItemNum.Add(-1)
		return nil
	}
	return KeyNotFound
}

// GetSize 获取缓存大小
func (scObj *CacheObj) GetSize() int {
	return int(scObj.currItemNum.Load())
}

// ClearAll 清空缓存
func (scObj *CacheObj) ClearAll() error {
	scObj.dataMap.Clear()
	return nil
}

// clearExpiredCache 内部处理定期清除过期缓存，可能会有时间1秒的时间差
func clearExpiredCache(ctx context.Context, scObj *CacheObj) {
	var needDelList []string
	var tmpCacheItem cacheItem
	var ok bool
	var tmpNums, tmpI int
	var currTime int64
	var tmpTicker01 *time.Ticker
	time.Sleep(3 * time.Second)
	tmpTicker01 = time.NewTicker(time.Second * 1)
	needDelList = make([]string, scObj.maxItemNum)
	for {
		select {
		case <-ctx.Done():
			scObj.dataMap.Clear()
			scObj.currItemNum.Store(0)
			return
		case <-tmpTicker01.C:
			currTime = time.Now().UnixNano()
			tmpNums, tmpI = 0, 0
			scObj.dataMap.Range(func(key, value any) bool {
				if tmpCacheItem, ok = value.(cacheItem); ok {
					if tmpCacheItem.expireTime == 0 {
						return true
					}
					if tmpCacheItem.expireTime < currTime {
						needDelList[tmpNums] = key.(string)
						tmpNums++
					}
				}
				return true
			})
			for tmpI = 0; tmpI < tmpNums; tmpI++ {
				scObj.currItemNum.Add(-1)
				scObj.dataMap.Delete(needDelList[tmpI])
			}
		}
	}
}
